import { Injectable, Inject } from '@angular/core';
import { Subject } from 'rxjs';

import { HttpService } from '../http/http.service';
import { BASE_DICT_URL, DICT_BASE_STORAGE_NAME, LOCAL_DICT } from '../keys';

@Injectable({
  providedIn: 'root'
})
export class DictService<U, D> {

  // 保存多个字典库，每个字典库有多种类型的字典
  private dictStorageMap = new Map<string, { dictMap: Map<string, any[]>, keysBuffer?: Set<string>, timeoutSubject?: Subject<any> }>();
  // 本地字典表
  private localDictStorage = {};

  constructor(
    private httpService: HttpService<U>,
    @Inject(BASE_DICT_URL) private baseDict: string,
    @Inject(DICT_BASE_STORAGE_NAME) private dictBaseStorageName: string,
    @Inject(LOCAL_DICT) private localDict: any[] = []
  ) {
    this.localDictStorage = this.localDict.reduce((pre, curr) => {
      return Object.assign(pre, curr);
    }, {});
  }

  /** 从某个字典库中获取某种类型的字典列表,如果params有值时dictKeys不能包含本地词典表key(以$开头) */
  getDict(dictKeys: D[], storageName = this.dictBaseStorageName, params?): Promise<any[]> {
    if (params) {
      return this.fetchDictForParams(<any[]>dictKeys, storageName, params);
    } else {
      const dictStorage = this.getDictStorage(storageName);

      const [fetchKeys, cacheList] = dictKeys.reduce((pre, currKey) => {
        const list = this.getList(dictStorage, <any>currKey);
        if (list.length === 0) {
          pre[0].push(currKey);
        } else {
          pre[1].push(list);
        }
        return pre;
      }, [[], []]);

      if (fetchKeys.length === 0) {
        return Promise.resolve(JSON.parse(JSON.stringify(cacheList)));
      }

      return this.fetchDict(<any[]>fetchKeys, storageName).then(() => {
        const dictList = dictKeys.map((k) => {
          return this.getList(dictStorage, <any>k);
        });
        return JSON.parse(JSON.stringify(dictList));
      });
    }
  }

  /** 从某个字典库中获取某种类型的字典中某个字典值 */
  getValue(dictKey: D, dictValue: string, storageName = this.dictBaseStorageName, params?): Promise<string> {
    if (dictValue === undefined || dictValue === null) {
      return Promise.resolve('');
    }

    const dictStorage = this.getDictStorage(storageName);

    if (!params && dictStorage.dictMap.has(<any>dictKey)) {
      return Promise.resolve(this._getValue(dictStorage.dictMap, <any>dictKey, dictValue));
    } else {
      return this.getDict([dictKey], storageName, params).then((list) => {
        if (params) {// 如果存在params不为空，则直接从结果中拿数据，不缓存
          return this._getValue(dictStorage.dictMap, <any>dictKey, dictValue, list);
        } else {
          return this._getValue(dictStorage.dictMap, <any>dictKey, dictValue);
        }
      });
    }
  }

  getLocalDict(key: string) {
    return this.localDictStorage[key] || [];
  }

  private getDictStorage(storageName: string) {
    let dictStorage = this.dictStorageMap.get(storageName);
    if (!dictStorage) {
      dictStorage = {
        dictMap: new Map<string, any[]>(),
        keysBuffer: null,
        timeoutSubject: null
      };
      this.dictStorageMap.set(storageName, dictStorage);
    }
    return dictStorage;
  }

  private _getValue(dictMap: Map<string, any[]>, dictKey: string, value: string, list?: any[]) {
    if (Array.isArray(list)) {
      // ...
    } else if (dictKey.startsWith('$')) {
      list = this.getLocalDict(dictKey);
    } else {
      list = dictMap.get(dictKey) || [];
    }

    const dictObj = list.find((d) => {
      // 兼容本地字典表，多value的情况
      if (Array.isArray(d.value)) {
        // tslint:disable-next-line:triple-equals
        return !!d.value.find(_d => _d == value);
      } else {
        // tslint:disable-next-line:triple-equals
        return d.value == value;
      }
    });

    if (dictObj) {
      return dictObj.label;
    } else {
      return '';
    }
  }


  private getList(dictStorage: { dictMap: Map<string, any[]> }, dictKey: string) {
    let list;

    if (dictKey.startsWith('$')) {
      list = this.getLocalDict(dictKey);
    } else {
      list = dictStorage.dictMap.get(dictKey) || [];
    }

    return list.map((v) => {
      if (Array.isArray(v.value)) {
        return { label: v.label, value: v.value[0] };
      } else {
        return v;
      }
    });
  }

  // 访问后台接口，加载某种类型的字典列表
  private fetchDict(dictKeys: string[], storage: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const dictStorage = this.dictStorageMap.get(storage);

      if (dictStorage.timeoutSubject) {
        dictKeys.forEach(k => dictStorage.keysBuffer.add(k));
        dictStorage.timeoutSubject.subscribe(() => resolve(), (e) => reject(e));
      } else {
        dictStorage.timeoutSubject = new Subject();
        dictStorage.keysBuffer = new Set(dictKeys);
        dictStorage.timeoutSubject.subscribe(() => resolve(), (e) => reject(e));

        setTimeout(() => {
          const groups = Array.from(dictStorage.keysBuffer);
          const timeoutSubject = dictStorage.timeoutSubject;
          dictStorage.keysBuffer = null;
          dictStorage.timeoutSubject = null;

          this.httpService.post(<any>this.baseDict, {
            groups: groups,
            dataSource: storage
          }).then((res) => {
            groups.forEach((key) => {
              const resDictList = res.data[key] || [];
              const dictList = resDictList.map((d) => {
                return {
                  label: d.configValue || d.dicLabel,
                  value: d.configKey || d.dicValue,
                  source: d
                };
              }).filter((d) => !!d.value);
              dictStorage.dictMap.set(key.toString(), dictList);
            });

            timeoutSubject.next();
            timeoutSubject.complete();
          }).catch(e => timeoutSubject.error(e));
        }, 500);
      }
    });
  }

  // 有额外请求参数的情况，不再对数据进行缓存
  private fetchDictForParams(dictKeys: string[], storageName: string, params: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.httpService.post(<any>this.baseDict, {
        groups: dictKeys,
        dataSource: storageName,
        ...params
      }).then((res) => {
        // 将对象转为数组
        const list = Object.keys(res.data).map((key) => {
          return res.data[key].map((d) => {
            return {
              label: d.label || d.configValue || d.dicLabel,
              value: d.value || d.configKey || d.dicValue,
              source: d
            };
          }).filter((d) => !!d.value);
        });
        resolve(list);
      }).catch(e => reject(e));
    });
  }
}
